import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;

public class ClientThread extends Thread
{
    private Socket clientSocket;
    private String user;
    private ObjectOutputStream out;
    private ServerTCP server;
    private ObjectInputStream in;    
    private Message request, response;    
    private boolean finish;
    private boolean update;
    
    public ClientThread(ServerTCP server, Socket s) throws Exception
    {
        this.server = server;
        this.clientSocket = s;        
        out = new ObjectOutputStream(s.getOutputStream());
        in = new ObjectInputStream(s.getInputStream());
        finish = false;
    }
    
    public String getUsername()
    {
        return user;
    }
    public Socket getSocket()
    {
        return clientSocket;
    }
    public void setUsername(String username)
    {
        user = username;
    }
    public void update(Message m)
    {
        try 
        {
            out.reset();
            out.writeObject(m);
            out.flush();
        }
        catch (IOException ex){System.out.println("EM CLIENT THREAD em update: " + ex);}
    }
    public void fileDeleted(String fileName)
    {
        try 
        {
            out.reset();
            out.writeObject(new Message(Constants.FILE_DELETED, new String[]{fileName}));
            out.flush();
        }
        catch (IOException ex){System.out.println("EM CLIENT THREAD em fileDeleted: " + ex);}
    }
    public void fileUpdated(String fileName)
    {
        try 
        {
            out.reset();
            out.writeObject(new Message(Constants.FILE_UPDATED, new String[]{fileName}));
            out.flush();
        }
        catch (IOException ex){System.out.println("EM CLIENT THREAD em fileUpdated: " + ex);}
    }
    
    @Override
    public void run()
    {
        System.out.println("Client Thread de " + clientSocket.getInetAddress().getHostAddress() + " waiting for messages...");
        while(!finish)
        {
            try
            {                
                response = null;
                update = false;
                System.out.println("Thread do cliente " + user + " recebeu: " + (request = (Message)in.readObject()).getMessage());

                switch(request.getMessage())
                {
                    case Constants.LOGIN:
                        response = server.loginClient(this,(String)request.getArguments()[0],(String)request.getArguments()[1]);
                        update = true;
                        break;
                    case Constants.REGISTER:
                        response = server.registerClient(this, (String)request.getArguments()[0],(String)request.getArguments()[1]);
                        update = true;
                        break;
                    case Constants.LOGOUT: //faz logout mas mantêm a Thread activa
                        server.logout(user);
                        user = null;
                        update = true;
                        break;
                    case Constants.EXIT: //faz logout e termina a Thread
                        if (user != null)
                        {
                            server.logout(user);
                            update = true;
                        }
                        finish = true;
                        break;
                    case Constants.UPLOAD_FILE:
                        new FileUpload().start();
                        break;
                    case Constants.DELETE_FILE:
                        response = server.deleteFile((String)request.getArguments()[0]);
                        update = true;
                        break;
                    case Constants.READ_COPY:
                        new FileDownload((String)request.getArguments()[0],Constants.READ_COPY).start();
                        break;
                    case Constants.EDIT_COPY:
                        new FileDownload((String)request.getArguments()[0],Constants.EDIT_COPY).start();
                        break;
                    case Constants.UPDATE_FILE:
                        new FileUpload().start();
                        break;
                    case Constants.DISCARD_EDIT_COPY:
                        server.discardEditCopy(user,(String)request.getArguments()[0]);
                        update = true;
                        break;
                    case Constants.DISCARD_READ_COPY:
                        server.discardReadCopy(user,(String)request.getArguments()[0]);
                        update = true;
                        break;
                    default:
                        System.exit(0);
                }
                if (response != null)
                {
                    out.reset();
                    out.writeObject(response);
                    out.flush();
                }
                if (update)
                {
                    server.update();
                }
            }
            catch(Exception e)
            {
                System.out.println("EM CLIENT THREAD: " + e);
            }
        }
        System.out.println("Thread fechada as " + new Date(System.currentTimeMillis()));
    }
    
    private class FileUpload extends Thread
    {
        private Socket uploadSocket;
        private String fileName;
        private String author;
        private Long fileLength;
        private Boolean update;
        private boolean allGood;
        private boolean updateServer;
        private byte[] bytearray;
        private int bytesRead;
        private InputStream is;
        private BufferedOutputStream bos;
        private ObjectOutputStream oos;
        private ObjectInputStream ois;
        private Message transferResult;
        
        public FileUpload()
        {
            this.allGood = true;
            this.updateServer = false;
            this.author = user;
        }
        
        @Override
        public void run()
        {
            try
            {
                uploadSocket = new Socket(clientSocket.getInetAddress(), Constants.TCP_PORT_FILE_UPLOAD);
                is = uploadSocket.getInputStream();
                ois = new ObjectInputStream(is);
                oos = new ObjectOutputStream(uploadSocket.getOutputStream());
                
                fileName = (String)ois.readObject();
                fileLength = (Long)ois.readObject();
                update = (Boolean)ois.readObject();
                
                if (update) //UPDATE
                {
                    if (!server.spaceExists(fileName,fileLength)) //NAO HA ESPACO
                    {
                        transferResult = new Message(Constants.INFO, new String[]{Constants.WARNING + Constants.NO_SPACE});
                        allGood = false;
                    }
                }
                else //NEW FILE
                {
                    if (server.fileExists(fileName)) //FICHEIRO JA EXISTE
                    {
                        transferResult = new Message(Constants.INFO, new String[]{Constants.WARNING + fileName + Constants.ALREADY_EXISTS_FILE});
                        allGood = false;
                    }
                    else if (!server.spaceExists(null,fileLength))
                    {
                        transferResult = new Message(Constants.INFO, new String[]{Constants.WARNING + Constants.NO_SPACE});
                        allGood = false;
                    }
                }
                if (allGood)
                {
                    oos.writeObject(Constants.SUCCESS);
                    oos.flush();
                    
                    bos = new BufferedOutputStream(new FileOutputStream(server.getDirectory() + fileName));
                    if (!update)
                    {
                        server.addTransferringFile(fileName);
                    }
                    do
                    {
                        bytearray = new byte[Constants.TRANSFER_SIZE];
                        bytesRead = is.read(bytearray,0,bytearray.length);
                        fileLength -= bytesRead;
                        //System.out.println(bytesRead + "    " + fileLength);
                        if (bytesRead != -1)
                        {
                            bos.write(bytearray, 0 , bytesRead);
                            bos.flush();
                        }
                    }
                    while(fileLength != 0); // enquanto nao ler tudo
                    
                    bos.close();
                    
                    if(!update)
                    {
                        server.addFile(fileName, author);
                        transferResult = new Message(Constants.INFO, new String[]{Constants.ADDED + fileName});
                    }
                    else
                    {
                        server.updateFile(fileName);
                        server.discardEditCopy(author, fileName);
                        transferResult = new Message(Constants.INFO, new String[]{Constants.UPDATED + fileName});
                    }
                    updateServer = true;
                }
                else
                {
                    oos.writeObject(Constants.INSUCCESS);
                    oos.flush();
                }
            }
            catch(Exception ex)
            {
                System.out.println("Em FILE_UPLOAD na CLIENT_THREAD: as " + new Date(System.currentTimeMillis()) + " " + ex);
                transferResult = new Message(Constants.INFO, new String[]{Constants.WARNING + Constants.ERROR + fileName});
            }
            finally
            {
                try
                {
                    out.writeObject(transferResult);
                    out.flush();
                    
                    System.out.println("Socket fechada às " + new Date(System.currentTimeMillis()));
                    oos.close();
                    ois.close();
                    is.close();
                    uploadSocket.close();
                }
                catch(Exception e){System.out.println(e);}
                
                if (updateServer)
                {
                    server.update();
                }
            }
            System.out.println("Thread fechada as " + new Date(System.currentTimeMillis()));
        }
    }
    private class FileDownload extends Thread
    {
        private String fileName;
        private String client;
        private String type;
        private Socket downloadSocket;
        private ObjectOutputStream oos;
        private byte[] bytearray;
        private BufferedInputStream bis;
        private OutputStream os;
        private int bytesRead;
        
        public FileDownload(String fileName, String type)
        {
            this.fileName = fileName;
            this.type = type;
            this.client = user;
        }

        @Override
        public void run() 
        {
            try
            {
                System.out.println("1");
                downloadSocket = new Socket(clientSocket.getInetAddress(), Constants.TCP_PORT_FILE_DOWNLOAD);
                System.out.println("2");
                os = downloadSocket.getOutputStream();
                System.out.println("3");
                oos = new ObjectOutputStream(os);
                System.out.println("4");
                
                oos.writeObject(server.getRepfile(fileName));
                oos.flush();
                System.out.println("5");
                oos.writeObject(type);
                oos.flush();
                System.out.println("6");
                bis = new BufferedInputStream(new FileInputStream(server.getDirectory() + fileName));
                System.out.println("7");
                do
                {
                    bytearray = new byte[Constants.TRANSFER_SIZE];
                    bytesRead = bis.read(bytearray,0,bytearray.length);
                    //System.out.println(bytesRead);
                    if (bytesRead != -1)
                    {
                        os.write(bytearray,0,bytesRead);
                        os.flush();
                    }
                }
                while(bytesRead == Constants.TRANSFER_SIZE);
                System.out.println("Transferência terminada as " + new Date(System.currentTimeMillis()));
                
                if (type.equals(Constants.READ_COPY))
                {
                    server.readCopydownloaded(client,fileName);
                }
                else
                {
                    server.editCopydownloaded(client,fileName);
                }
                server.update();
            }
            catch(Exception ex){System.out.println("Em FILE_DOWNLOAD na CLIENT_THREAD: as " + new Date(System.currentTimeMillis()) + " " + ex);}
            finally
            {
                try
                {                    
                    bis.close();
                    oos.close();
                    os.close();
                }
                catch(Exception e){System.out.println(e);}
            }
            System.out.println("Thread fechada as " + new Date(System.currentTimeMillis()));
        }        
    }
}
